# 调用下载函数，多线程序列下载的模块

from Download import *
from Functions import *


def download_whole_version(version: str, name: str, mode: str):
    """
    下载完整的版本。
    :param mode: Mojang、BMCLAPI或MCBBS。
    :param name: 指定的自定义名称。
    :param version: 指定的版本号。
    :return: 成功则返回True，错误则返回False。
    """
    tag = random_num_tag()

    logger.Logger.info(tag + "准备通过" + mode + "下载完整版本：" + version + "，命名为" + name)

    back_info_1 = search_new_version(version=version, mode=mode)

    logger.Logger.info(tag + "尝试将该版本添加至缓存记录中。")

    saved_info = local_files.game_versions_add(0, version_name=name, version_num=version, java_version="", java_path="")

    if saved_info:
        logger.Logger.info(tag + "添加至记录成功，即将开始下载。")
    else:
        logger.Logger.error(tag + "添加至记录失败，无法下载。")
        return False

    if not back_info_1:
        logger.Logger.warning(tag + "查询目标版本失败，无法下载。")
        return False

    version_json_url = back_info_1[1]

    logger.Logger.info(tag + "已通过查询获取到目标版本JSON的url。即将下载该JSON文件，并存储至versions文件夹内。")
    logger.Logger.info(tag + "版本JSON的url：" + version_json_url)

    version_json_path = config.versions_path + name + "\\"

    back_info_2 = from_url_download_json(url=version_json_url, name=name, path=version_json_path, mode=mode, sha1="")

    if not back_info_2:
        logger.Logger.warning(tag + "下载目标版本JSON失败，无法下载。")
        return False

    version_json_path = version_json_path + name + ".json"

    logger.Logger.info(tag + "版本JSON下载完毕，即将开始解析JSON并下载更多文件。")

    textIO = None

    try:
        textIO = open(file=version_json_path, mode="r", encoding="utf-8")
        version_json_data = json.loads(textIO.read())
        textIO.close()
    except:
        logger.Logger.error(tag + "读取并解析版本JSON时出现异常问题，下载终止。")
        if textIO:
            textIO.close()
        return False

    logger.Logger.info(tag + "正在分配版本jar文件下载。")
    downloads_list = dict(version_json_data['downloads'])  # 版本jar部分
    client_info = dict(downloads_list['client'])
    client_sha1 = str(client_info['sha1'])
    client_url = str(client_info['url'])

    version_jar_path = config.versions_path + name + "\\"

    func.threading_manager.acquire()
    threading.Thread(target=from_url_download_jar, args=(client_url, name, version_jar_path, mode, client_sha1),
                     name="Version_jar_downloader", daemon=True).start()
    func.threading_manager.release()

    logger.Logger.info(tag + "版本jar文件下载已分配，准备分配libraries中的库文件下载。")
    libraries_list = list(version_json_data['libraries'])  # libraries部分
    func.threading_manager.acquire()
    threading.Thread(target=download_libraries, args=(libraries_list, mode),
                     name="Libraries_manager", daemon=True).start()
    func.threading_manager.release()

    logger.Logger.info(tag + "正在准备下载版本资源索引JSON。")

    assets_info = dict(version_json_data['assetIndex'])
    assets_id = str(assets_info['id'])
    assetIndex_url = str(assets_info['url'])
    assetIndex_sha1 = str(assets_info['sha1'])

    assetIndex_saved_name = assets_id

    assetIndex_saved_path = config.assets_path + "indexes\\"

    back_bool = from_url_download_json(url=assetIndex_url, name=assetIndex_saved_name, path=assetIndex_saved_path,
                                       mode=mode, sha1=assetIndex_sha1)

    if not back_bool:
        logger.Logger.error(tag + "下载版本资源索引JSON时出现错误，下载无法继续。")
        return False

    logger.Logger.info(tag + "下载版本资源索引JSON成功。")

    assetIndex_real_path = assetIndex_saved_path + assetIndex_saved_name + ".json"

    assetIndex_data = open(file=assetIndex_real_path, mode="r")
    assets_info = json.loads(assetIndex_data.read())
    objects_dict = assets_info['objects']
    assetIndex_data.close()
    logger.Logger.info(tag + "成功处理版本资源索引JSON，开始解析并分配下载任务。")

    func.threading_manager.acquire()
    threading.Thread(target=download_assets, args=(objects_dict, mode), name="Assets_manager", daemon=True).start()
    func.threading_manager.release()

    logger.Logger.debug(tag + "任务分配完毕。")
    logger.Logger.info(tag + "所有下载任务全部分配完毕，等候下载进行。")

    return True


def check_whole_version(name: str, mode: str):
    """
    检查版本是否完整，如不完整则自动补全。
    :param mode: Mojang、BMCLAPI或MCBBS。
    :param name: 指定的自定义名称。
    :return: 成功则返回True，错误则返回False。
    """
    tag = random_num_tag()

    logger.Logger.info(tag + "准备通过" + mode + "补全版本：" + name + "。")

    version_json_path = config.versions_path + name + "\\" + name + ".json"

    logger.Logger.debug(tag + "准备读取并解析版本JSON。")

    textIO = None

    try:
        textIO = open(file=version_json_path, mode="r", encoding="utf-8")
        version_json_data = json.loads(textIO.read())
        textIO.close()
    except:
        logger.Logger.error(tag + "读取并解析版本JSON时出现异常问题，下载终止。")
        if textIO:
            textIO.close()
        return False

    logger.Logger.info(tag + "正在分配libraries中的库文件进行校验。")
    libraries_list = list(version_json_data['libraries'])  # libraries部分
    func.threading_manager.acquire()
    threading.Thread(target=check_libraries, args=(libraries_list, mode),
                     name="Libraries_checker", daemon=True).start()
    func.threading_manager.release()

    logger.Logger.info(tag + "正在准备校验版本资源索引JSON。")

    assets_info = dict(version_json_data['assetIndex'])
    assets_id = str(assets_info['id'])
    assetIndex_url = str(assets_info['url'])
    assetIndex_sha1 = str(assets_info['sha1'])

    assetIndex_saved_name = assets_id

    assetIndex_saved_path = config.assets_path + "indexes\\"

    back_bool = from_url_check_json(url=assetIndex_url, name=assetIndex_saved_name, path=assetIndex_saved_path,
                                    mode=mode, sha1=assetIndex_sha1)

    if not back_bool:
        logger.Logger.error(tag + "补全版本资源索引JSON时出现错误，已跳过该项。")

    else:
        logger.Logger.info(tag + "下载版本资源索引JSON成功。")

    assetIndex_real_path = assetIndex_saved_path + assetIndex_saved_name + ".json"

    assetIndex_data = open(file=assetIndex_real_path, mode="r")
    assets_info = json.loads(assetIndex_data.read())
    objects_dict = assets_info['objects']
    assetIndex_data.close()
    logger.Logger.info(tag + "成功处理版本资源索引JSON，开始解析并分配下载任务。")

    func.threading_manager.acquire()
    threading.Thread(target=download_assets, args=(objects_dict, mode), name="Assets_checker", daemon=True).start()
    func.threading_manager.release()

    logger.Logger.debug(tag + "任务分配完毕。")
    logger.Logger.info(tag + "所有校验任务全部分配完毕，等候校验或下载进行。")

    return True


def download_libraries(libraries_list: list, mode: str):
    """
    这个函数可以根据从版本JSON中提取出的libraries键后跟值的列表来下载该版本的libraries库文件。
    :param libraries_list: 需要为一个列表，其中每一项都是字典。
    :param mode: Mojang、BMCLAPI、MCBBS。
    :return: 此函数不返回值。
    """
    tag = random_num_tag()
    logger.Logger.info(tag + "开始根据该版本JSON文件内的信息补全libraries。")
    file_temp_2 = {}
    dividing_str = "\\"
    for i in libraries_list:
        file_temp_1 = i['downloads']
        if "classifiers" in file_temp_1:
            file_temp_1_ = file_temp_1['classifiers']
            if "natives-windows-64" in file_temp_1_:
                file_temp_2 = file_temp_1_['natives-windows-64']
            elif "natives-windows" in file_temp_1_:
                file_temp_2 = file_temp_1_['natives-windows']
            else:
                continue
        elif "artifact" in file_temp_1:
            file_temp_2 = file_temp_1['artifact']
        if not file_temp_2:
            continue
        file_url = str(file_temp_2['url'])
        file_name__ = str(file_temp_2['path'])
        temp_1 = file_name__.split("/")
        temp_2 = temp_1.pop()
        file_name = temp_2.replace(".jar", "")
        file_path = config.libraries_path + dividing_str.join(temp_1) + "\\"
        file_sha1 = str(file_temp_2['sha1'])
        logger.Logger.debug(tag + "即将调用一个线程进行下载。")
        T = threading.Thread(target=from_url_download_jar, args=(file_url, file_name, file_path, mode, file_sha1),
                             name="Download_libraries", daemon=True)
        time.sleep(0.1)
        T.start()
    return None


def check_libraries(libraries_list: list, mode: str):
    """
    这个函数可以根据从版本JSON中提取出的libraries键后跟值的列表来校验该版本的libraries库文件。
    :param libraries_list: 需要为一个列表，其中每一项都是字典。
    :param mode: Mojang、BMCLAPI、MCBBS。
    :return: 此函数不返回值。
    """
    tag = random_num_tag()
    logger.Logger.info(tag + "开始根据该版本JSON文件内的信息校验libraries。")
    file_temp_2 = {}
    dividing_str = "\\"
    for i in libraries_list:
        file_temp_1 = i['downloads']
        if "classifiers" in file_temp_1:
            file_temp_1_ = file_temp_1['classifiers']
            if "natives-windows-64" in file_temp_1_:
                file_temp_2 = file_temp_1_['natives-windows-64']
            elif "natives-windows" in file_temp_1_:
                file_temp_2 = file_temp_1_['natives-windows']
            else:
                continue
        elif "artifact" in file_temp_1:
            file_temp_2 = file_temp_1['artifact']
        if not file_temp_2:
            continue
        file_url = str(file_temp_2['url'])
        file_name__ = str(file_temp_2['path'])
        temp_1 = file_name__.split("/")
        temp_2 = temp_1.pop()
        file_name = temp_2.replace(".jar", "")
        file_path = config.libraries_path + dividing_str.join(temp_1) + "\\"
        file_sha1_d = str(file_temp_2['sha1'])

        try:
            file_temp_c = open(file=file_path, mode="rb")
        except:
            logger.Logger.info(tag + "发现缺失的库文件：" + file_path + "准备进行下载。")

        else:
            file_sha1_c = hashlib.sha1(file_temp_c.read()).hexdigest()
            file_temp_c.close()
            logger.Logger.debug(tag + "成功计算该已存在文件的sha1校验码：" + file_sha1_d + "，准备进行比对。")

            if file_sha1_c == file_sha1_d:
                logger.Logger.debug(tag + "该文件在版本JSON中的sha1校验码：" + file_sha1_d + "，文件校对成功，无需下载。")
                continue
            else:
                logger.Logger.debug(tag + "该文件在版本JSON中的sha1校验码：" + file_sha1_d + "，文件校对失败，准备重新下载。")

        finally:
            logger.Logger.debug(tag + "即将调用一个线程进行下载。")
            func.threading_manager.acquire()
            T = threading.Thread(target=from_url_download_jar, args=(file_url, file_name, file_path, mode, file_sha1_d),
                                 name="Download_libraries", daemon=True)
            time.sleep(0.1)
            func.threading_manager.release()
            T.start()
    return None


def from_url_check_json(url: str, name: str, path: str, mode: str, sha1: str):
    """
    此函数仅支持校验一个JSON文件，并在校验失败时重新下载。
    :param url: 指定JSON的url。
    :param name: 指定的保存名称，不包括文件后缀（自动添加后缀为.json）。
    :param path: 指定的保存地址，并不是该JSON文件预计的绝对路径。
    :param mode: 指定的下载方式，即Mojang，……
    :param sha1: 使用sha1校验码进行校验，若校验失败则重新下载。
    :return: 如果下载成功则返回True，否则返回False。
    """
    tag = random_num_tag()
    logger.Logger.debug(tag + "即将开始以" + mode + "方式校验版本：" + name + "的资源索引JSON，位于：" + path + "。")

    file_path = path + name

    try:
        file_temp_c = open(file=file_path, mode="r")
    except:
        logger.Logger.debug(tag + "版本JSON文件似乎缺失，准备重新下载。")

    else:
        sha1_c = hashlib.sha1(file_temp_c.read()).hexdigest()
        file_temp_c.close()
        logger.Logger.debug(tag + "成功计算该已存在JSON的sha1校验码：" + sha1_c + "，准备进行比对。")

        if sha1_c == sha1:
            logger.Logger.debug(tag + "该文件在版本JSON中的sha1校验码：" + sha1 + "，文件校对成功，无需下载。")
            return True
        else:
            logger.Logger.warning(tag + "该文件在版本JSON中的sha1校验码：" + sha1 + "，文件校对失败，准备重新下载。")

    finally:

        back_info = from_url_download_json(url, name, path, mode, sha1)

        if back_info:
            logger.Logger.debug(tag + "版本JSON下载校验完毕。")
            return True
        else:
            logger.Logger.warning(tag + "版本JSON下载校验失败，该项已被跳过。")
            return False


def download_assets(objects_list: dict, mode: str):
    """
    这个函数可以根据从版本资源索引JSON中提取出的列表来下载该版本的资源文件。
    :param objects_list: 需要为一个列表，其中每一项都是字典。
    :param mode: Mojang、BMCLAPI、MCBBS。
    :return: 此函数不返回值。
    """
    tag = random_num_tag()
    for i in objects_list.values():
        download_hash = str(i['hash'])
        logger.Logger.debug(tag + "准备开始一项assets资源文件的下载：" + download_hash)
        data_temp_1 = download_hash[0: 2]
        data_temp_2 = data_temp_1 + "/" + download_hash
        download_saved_path = config.assets_path + "objects\\" + data_temp_1 + "\\"
        logger.Logger.debug(tag + "从资源索引JSON中成功读取一个下载规则，准备开始下载。")
        func.threading_manager.acquire()
        threading.Thread(target=from_url_download_asset, name="Asset_downloader", daemon=True,
                         args=(data_temp_2, download_hash, download_saved_path, mode)).start()
        func.threading_manager.release()

    logger.Logger.debug(tag + "所有资源文件已全部遍历，下载任务初步完成。")

    return None


def get_versions_list(mode: str):
    """
    此函数用于向指定的下载源访问，以查询Minecraft版本列表，会将版本信息打包并返回。
    :param mode: 查询指定的下载源。
    :return: 理想状态应返回一个字典，其中以latest标记最近更新的一个正式版和一个快照版组成的嵌套字典，以release标记正式版列表，以snapshot标记快照版列表。
    """
    releases_list = []
    snapshots_list = []

    logger.Logger.debug("开始以" + mode + "方式查询完整的版本列表。")
    if mode == "Mojang":
        url = "https://launchermeta.mojang.com/mc/game/version_manifest.json"
    elif mode == "BMCLAPI":
        url = "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json"
    elif mode == "MCBBS":
        url = "https://download.mcbbs.net/mc/game/version_manifest.json"
    else:
        logger.Logger.warning("未知下载源，查询版本列表取消。")
        return False

    logger.Logger.info("正在根据指定下载源" + mode + "查询版本列表：" + url)

    response = requests.get(url=url)

    if not response.status_code == 200:
        logger.Logger.warning("指定下载源响应失败，无法查询版本列表。")
        return False

    json_data = response.json()

    logger.Logger.info("成功获取版本列表，开始解析。")

    latest_dict = json_data["latest"]

    for i in json_data["versions"]:
        if i["type"] == "release":
            i["time"] = time_translation(i["time"])
            i["releaseTime"] = time_translation(i["releaseTime"])
            i.pop("url")
            releases_list.append(i)
        elif i["type"] == "snapshot":
            i["time"] = time_translation(i["time"])
            i["releaseTime"] = time_translation(i["releaseTime"])
            i.pop("url")
            snapshots_list.append(i)

    if latest_dict["snapshot"] == latest_dict["release"]:
        latest_dict["snapshot"] = snapshots_list[0]["id"]

    versions_dict = {"latest": latest_dict, "release": releases_list, "snapshot": snapshots_list}

    logger.Logger.info("版本列表解析完毕，返回成功。")

    return versions_dict


if __name__ == "__main__":
    get_versions_list("MCBBS")
